package com.triple.interviewexpert.sentinel;

import cn.hutool.core.io.FileUtil;
import com.alibaba.csp.sentinel.datasource.*;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.File;
import java.util.Arrays;
import java.util.List;

/**
 * Sentinel 限流熔断规则管理器
 */
@Component
public class SentinelRulesManager {

    @PostConstruct
    public void init() throws Exception {
        // 初始化限流规则
        initFlowRules();
        // 初始化降级规则
        initDegradeRules();
        // 拉模式的规则配置本地持久化
        // listenRules();
    }

    /**
     * 限流规则
     */
    public void initFlowRules() {
        /**
         * 已经将规则写入Nacos
        // 单 IP 查看题目列表限流规则
        ParamFlowRule listQuestionVOByPageRule = new ParamFlowRule(SentinelConstant.listQuestionVOByPage)
                .setParamIdx(0) // 对第 0 个参数限流，即 IP 地址
                .setCount(60) // 每分钟最多 60 次
                .setDurationInSec(60); // 规则的统计周期为 60 秒
        // 单 IP 题目搜索限流规则
        ParamFlowRule searchQuestionVOByPageRule = new ParamFlowRule(SentinelConstant.searchQuestionVOByPage)
                .setParamIdx(0) // 对第 0 个参数限流，即 IP 地址
                .setCount(100) // 每分钟最多 100 次
                .setDurationInSec(60); // 规则的统计周期为 60 秒
        // 单 IP 用户注册限流规则
        ParamFlowRule userRegisterRule = new ParamFlowRule(SentinelConstant.userRegister)
                .setParamIdx(0) // 对第 0 个参数限流，即 IP 地址
                .setCount(5) // 每分钟最多 5 次
                .setDurationInSec(60); // 规则的统计周期为 60 秒
        // 单 IP 用户登录限流规则
        ParamFlowRule userLoginRule = new ParamFlowRule(SentinelConstant.userLogin)
                .setParamIdx(0) // 对第 0 个参数限流，即 IP 地址
                .setCount(10) // 每分钟最多 10  次
                .setDurationInSec(60); // 规则的统计周期为 60 秒
        // 加载手动编码的规则
        ParamFlowRuleManager.loadRules(Arrays.asList(
                listQuestionVOByPageRule,
                searchQuestionVOByPageRule,
                userRegisterRule,
                userLoginRule
        ));
        */
        // 推模式: 加载 Nacos 中配置的限流规则
        String remoteAddress = SentinelConstant.NacosRemoteAddress;
        String groupId = SentinelConstant.NacosGroupId;
        String flowDataId = SentinelConstant.NacosFlowDataId;
        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, flowDataId,
                source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {}));
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
        // 热点限流
        String paramFlowDataId = SentinelConstant.NacosParamFlowDataId;
        ReadableDataSource<String, List<ParamFlowRule>> paramFlowRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, paramFlowDataId,
                source -> JSON.parseObject(source, new TypeReference<List<ParamFlowRule>>() {}));
        ParamFlowRuleManager.register2Property(paramFlowRuleDataSource.getProperty());
    }

    /**
     * 降级规则
     */
    public void initDegradeRules() {
        /**
         * 已经将规则写入Nacos
        // 查看题目列表熔断规则
        DegradeRule listQuestionVOByPageErrorRateRule = new DegradeRule(SentinelConstant.listQuestionVOByPage)
                .setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType())
                .setCount(0.1) // 异常率大于 10%
                .setTimeWindow(60) // 熔断持续时间 60 秒
                .setStatIntervalMs(30 * 1000) // 统计时长 30 秒
                .setMinRequestAmount(10); // 最小请求数
        DegradeRule listQuestionVOByPageSlowCallRule = new DegradeRule(SentinelConstant.listQuestionVOByPage)
                .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
                .setSlowRatioThreshold(0.2) // 慢调用比例大于 20%
                .setTimeWindow(60) // 熔断持续时间 60 秒
                .setStatIntervalMs(30 * 1000) // 统计时长 30 秒
                .setMinRequestAmount(10) // 最小请求数
                .setCount(3000); // 响应时间超过 3 秒
        // 题目搜索熔断规则
        DegradeRule searchQuestionVOByPageErrorRateRule = new DegradeRule(SentinelConstant.searchQuestionVOByPage)
                .setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType())
                .setCount(0.1) // 异常率大于 10%
                .setTimeWindow(60) // 熔断持续时间 60 秒
                .setStatIntervalMs(30 * 1000) // 统计时长 30 秒
                .setMinRequestAmount(10); // 最小请求数
        DegradeRule searchQuestionVOByPageSlowCallRule = new DegradeRule(SentinelConstant.searchQuestionVOByPage)
                .setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType())
                .setSlowRatioThreshold(0.2) // 慢调用比例大于 20%
                .setTimeWindow(60) // 熔断持续时间 60 秒
                .setStatIntervalMs(30 * 1000) // 统计时长 30 秒
                .setMinRequestAmount(10) // 最小请求数
                .setCount(3000); // 响应时间超过 3 秒
        // 用户注册熔断规则
        DegradeRule userRegisterErrorRateRule = new DegradeRule(SentinelConstant.userRegister)
                .setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType())
                .setCount(0.05) // 异常率大于 5%
                .setTimeWindow(60) // 熔断持续时间 60 秒
                .setStatIntervalMs(300 * 1000) // 统计时长 300 秒
                .setMinRequestAmount(10); // 最小请求数
        // 用户登录熔断规则
        DegradeRule userLoginErrorRateRule = new DegradeRule(SentinelConstant.userLogin)
                .setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType())
                .setCount(0.1) // 异常率大于 10%
                .setTimeWindow(60) // 熔断持续时间 60 秒
                .setStatIntervalMs(60 * 1000) // 统计时长 60 秒
                .setMinRequestAmount(10); // 最小请求数
        DegradeRule userLoginErrorCountRule = new DegradeRule(SentinelConstant.userLogin)
                .setGrade(CircuitBreakerStrategy.ERROR_COUNT.getType())
                .setCount(1000) // 异常次数大于 1000 次
                .setTimeWindow(60) // 熔断持续时间 60 秒
                .setStatIntervalMs(60 * 1000) // 统计时长 60 秒
                .setMinRequestAmount(10); // 最小请求数
        // 加载规则
        DegradeRuleManager.loadRules(Arrays.asList(
                listQuestionVOByPageErrorRateRule,
                listQuestionVOByPageSlowCallRule,
                searchQuestionVOByPageErrorRateRule,
                searchQuestionVOByPageSlowCallRule,
                userRegisterErrorRateRule,
                userLoginErrorRateRule,
                userLoginErrorCountRule
        ));
        */
        // 推模式: 加载 Nacos 中配置的熔断规则
        String remoteAddress = SentinelConstant.NacosRemoteAddress;
        String groupId = SentinelConstant.NacosGroupId;
        String dataId = SentinelConstant.NacosDegradeDataId;
        ReadableDataSource<String, List<DegradeRule>> DegradeRuleDataSource = new NacosDataSource<>(remoteAddress, groupId, dataId,
                source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>() {}));
        DegradeRuleManager.register2Property(DegradeRuleDataSource.getProperty());
    }

    /**
     * 拉模式: 持久化配置为本地文件
     */
    public void listenRules() throws Exception {
        // 获取项目根目录
        String rootPath = System.getProperty("user.dir");
        // sentinel 目录路径
        File sentinelDir = new File(rootPath, "sentinel");
        // 目录不存在则创建
        if (!FileUtil.exist(sentinelDir)) {
            FileUtil.mkdir(sentinelDir);
        }
        // 规则文件路径
        String flowRulePath = new File(sentinelDir, "FlowRule.json").getAbsolutePath();
        String degradeRulePath = new File(sentinelDir, "DegradeRule.json").getAbsolutePath();

        // Data source for FlowRule
        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new FileRefreshableDataSource<>(flowRulePath, flowRuleListParser);
        // Register to flow rule manager.
        FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
        WritableDataSource<List<FlowRule>> flowWds = new FileWritableDataSource<>(flowRulePath, this::encodeJson);
        // Register to writable data source registry so that rules can be updated to file
        WritableDataSourceRegistry.registerFlowDataSource(flowWds);

        // Data source for DegradeRule
        FileRefreshableDataSource<List<DegradeRule>> degradeRuleDataSource
                = new FileRefreshableDataSource<>(
                degradeRulePath, degradeRuleListParser);
        DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
        WritableDataSource<List<DegradeRule>> degradeWds = new FileWritableDataSource<>(degradeRulePath, this::encodeJson);
        // Register to writable data source registry so that rules can be updated to file
        WritableDataSourceRegistry.registerDegradeDataSource(degradeWds);
    }

    private Converter<String, List<FlowRule>> flowRuleListParser = source -> JSON.parseObject(source,
            new TypeReference<List<FlowRule>>() {
            });
    private Converter<String, List<DegradeRule>> degradeRuleListParser = source -> JSON.parseObject(source,
            new TypeReference<List<DegradeRule>>() {
            });

    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }

}
